/*
 *  Copyleft( c ), 2010, @ xiyou linux group.
 *  All rights reserved.
 *
 *  File name: xylgkg.c.
 *  Programmers' name: Harry Wei.
 *  Start time: 2010, 8, 1.
 *  Finish time: 2010, 11, 7.
 *
 *  Programme introduction: Just program a simple virtual fs.
 */
#include <linux/module.h>		/*U know it is a module inserted in kernel :) */
#include <linux/fs.h>			/*some important strcture in it, like inode structure.*/
#include <linux/fsnotify.h>		/*notify some information between dentry and inode*/
#include <linux/init.h>			/*module init and exit*/
#include <linux/namei.h>		/*lookup_one_len in it*/
#include <linux/magic.h>		/*some fs's magic number in it*/
#include <linux/mount.h>		/*mount infor mation*/
#include <linux/kobject.h>
#include <linux/pagemap.h>

#define XYLGKGFS_MAGIC		0x3966477

static struct vfsmount *xylgkgfs_mount;
static int xylgkgfs_mount_count;
static bool xylgkgfs_registered;

static const struct super_operations xylgkgfs = {
	.statfs = simple_statfs,
	.drop_inode = generic_delete_inode,
};

static ssize_t xylgkgfs_read_file(struct file *file, 
			const char __user *buf, 
			size_t    count, loff_t *ppos )
{
	return 0;
}

static ssize_t xylgkgfs_write_file(struct file *file, 
			const char __user *buf, 
			size_t count, loff_t *ppos)
{
	return count;
}

static ssize_t xylgkgfs_open_file(struct inode *inode, 
			struct file *file )
{
	if (inode->i_private)
		file->private_data = inode->i_private;
	return 0;
}

const struct file_operations xylgkg_file_operations = {
	.read = xylgkgfs_read_file,
	.write = xylgkgfs_write_file,
	.open = xylgkgfs_open_file,
};

static struct inode *xylgkgfs_get_inode(struct super_block *sb, 
			int mode, dev_t dev )
{
	struct inode *inode = new_inode(sb);

	if (inode) {
		inode->i_mode = mode;
		inode->i_uid = 0;
		inode->i_gid = 0;
		inode->i_blocks = 0;
	    	inode->i_atime = inode->i_mtime = CURRENT_TIME;
	    	inode->i_fop = &xylgkg_file_operations;
	}
    	return inode;
}

static int xylgkgfs_mknod(struct inode *dir, 
			struct dentry *dentry, 
			int mode, dev_t dev)
{
	struct inode *inode;
	int error = -EPERM;

	if (dentry->d_inode) {
		return( -EEXIST );
	}
	inode = xylgkgfs_get_inode(dir->i_sb, mode, dev);
	if (inode) {
		d_instantiate( dentry, inode );
		dget( dentry );
		error = 0;
	}
    	return error;
}

static int xylgkgfs_creat(struct inode *dir, 
			struct dentry *dentry, int mode)
{
	int    res;
	
	mode = (mode & S_IALLUGO) | S_IFREG;
	res = xylgkgfs_mknod(dir, dentry, mode, 0);
	if (!res) {
		fsnotify_create(dir, dentry);
	}
	return res;
}

static inline int xylgkgfs_fill_super(struct super_block *sb, 
			void *data, int silent)
{
	static struct tree_descr xylgkgfs_files[] = {{""}};
	return simple_fill_super(sb, XYLGKGFS_MAGIC, 
					xylgkgfs_files);
}

static int xylgkgfs_get_sb(struct file_system_type *xylgkgfs_fs_type, 
			int flags, const char *dev_name, void *data, 
			struct vfsmount *mnt )
{
	return get_sb_single(xylgkgfs_fs_type, flags, 
		    	data, xylgkgfs_fill_super, mnt);
}

static struct file_system_type xylgkgfs_fs_type = {
	.owner = THIS_MODULE,
	.name = "xylgkgfs",
	.get_sb = xylgkgfs_get_sb,
	.kill_sb = kill_litter_super,
};

static int xylgkgfs_create_by_name(const char *name, mode_t mode, 
			struct dentry *parent, struct dentry **dentry)
{
	int error = 0;
	
	if (!parent)
		if(xylgkgfs_mount && xylgkgfs_mount->mnt_sb)
			parent = xylgkgfs_mount->mnt_sb->s_root;
	if (!parent)
		return -EFAULT;
	*dentry = NULL;
	*dentry = lookup_one_len(name, parent, strlen(name));
	error = xylgkgfs_creat(parent->d_inode, *dentry, mode);
	dput(*dentry);
	return error;
}

struct dentry *xylgkgfs_create_file(const char *name, mode_t mode, 
			struct dentry *parent, void *data, 
			const struct file_operations *fops)
{
	int error;
	struct dentry *dentry = NULL;

	error = simple_pin_fs(&xylgkgfs_fs_type, 
			&xylgkgfs_mount, &xylgkgfs_mount_count);
	if (error) {
		goto exit;
	}
	error = xylgkgfs_create_by_name(name, mode, parent, &dentry);
	if (error) {
		dentry = NULL;
		simple_release_fs(&xylgkgfs_mount, &xylgkgfs_mount_count);
		goto exit;
	}
	if (dentry->d_inode)
		if( data )
			dentry->d_inode->i_private = data;
	if(fops)
		dentry->d_inode->i_fop = fops;
exit:
	return(dentry);
}
EXPORT_SYMBOL_GPL(xylgkgfs_create_file);

struct dentry *xylgkgfs_create_dir(const char *name, 
			struct dentry *parent)
{
	return(xylgkgfs_create_file(name, S_IFDIR | S_IRWXU 
			    | S_IRUGO | S_IXUGO, parent, 
			    NULL, NULL));
}
EXPORT_SYMBOL_GPL(xylgkgfs_create_dir);

static int __init xylgkgfs_init(void)
{
	int retval;

	retval = register_filesystem(&xylgkgfs_fs_type);
	if (!retval) {
		xylgkgfs_mount = vfs_kern_mount(
				&xylgkgfs_fs_type, MS_KERNMOUNT, 
				(&xylgkgfs_fs_type)->name, NULL);
		if (IS_ERR(xylgkgfs_mount)) {
			printk(KERN_ERR "xylgkgfs: can not mount!\n");
			retval = PTR_ERR(xylgkgfs_mount);
			xylgkgfs_mount = NULL;
			unregister_filesystem( &xylgkgfs_fs_type );
			return(retval);
		}
	}
	return 0;
}

static void __exit xylgkgfs_exit(void)
{
	xylgkgfs_registered = false;
	simple_release_fs(&xylgkgfs_mount, &xylgkgfs_mount_count);
	unregister_filesystem( &xylgkgfs_fs_type );
}

module_init( xylgkgfs_init );
module_exit( xylgkgfs_exit );
MODULE_LICENSE( "GPL" );
